<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link href="../css/main.css" media="all" rel="stylesheet" type="text/css" />
<link href="../css/highlight.css" media="all" rel="stylesheet" type="text/css" />
<link href="../css/print.css" media="print" rel="stylesheet" type="text/css" />
<title>第18章 - パフォーマンス</title>
</head>

<body>
<div class="navigation">

<table width="100%">
<tr>
<td width="40%" align="left"><a href="17-Extending-Symfony.html">前の章</a></td>
<td width="20%" align="center"><a href="index.html">ホーム</a></td>
<td width="40%" align="right"><a href="19-Mastering-Symfony-s-Configuration-Files.html">次の章</a></td>
</tr>
</table>
<hr/>
</div>

<div>
<a name="chapter.18.performance" id="chapter.18.performance"></a><h1>第18章 - パフォーマンス</h1>

<p>あなたのWebサイトが大勢の訪問者を引き寄せることを望むのであれば、パフォーマンスと最適化の問題は開発フェーズにおいて重要な要因です。ご安心ください、パフォーマンスはつねにsymfonyのコア開発者の最重要の関心事です。</p>

<p>開発過程の加速によって得られた利益が多少のオーバーヘッドに終わる一方で symfonyのコア開発者はパフォーマンスの要件をつねに認識してきました。したがって、すべてのクラスとメソッドは念入りに点検され可能なかぎり速く動作するように最適化されてきました。symfonyの利用の有無にかかわらず「hello,world」を表示するために必要な時間を比較することで測定できる基本的なオーバーヘッドは最小です。結果として、symfonyフレームワークはスケーラブルで、負荷テストによく対応します。最高の証明として、きわめて膨大なトラフィック量を占めるいくつかのWebサイト(つまり何百万ものアクティブな購読者とサーバーに負荷を与える多くのAjaxインタラクションをかかえるWebサイト)がsymfonyを利用しており、パフォーマンスにとても満足しています。symfonyで開発されたWebサイトの一覧はwiki(<a href="http://trac.symfony-project.org/wiki/ApplicationsDevelopedWithSymfony">http://trac.symfony-project.org/wiki/ApplicationsDevelopedWithSymfony</a>)で確認してください。</p>

<p>しかし、もちろん、膨大なトラフィックを占めるWebサイトはサーバーファームを拡張し運営者が最適だと思うハードウェアにアップグレードする方法を持つことがよくあります。これを行うリソースを持たない場合、もしくはsymfonyフレームワークのフルパワーがつねに思いどおりに利用できることを確かめたい場合、symfony製のアプリケーションをもっと加速するために利用できる調整方法がいくつかあります。この章ではフレームワークのすべてのレベルで推奨されるパフォーマンスの最適化方法のいくつかのリストを示します。これらの大半は上級ユーザー向けです。中にはすでに以前の章で触れられているものもありますが、一度にこれらすべてが有用であることを理解することになります。</p>

<div class="toc">
<dl>
<dt><a href="#tweaking.the.server">18.1. サーバーを調整する</a></dt>
<dt><a href="#tweaking.the.model">18.2. モデルを調整する</a></dt>
<dd><dl>
<dt><a href="#optimizing.propel.integration">18.2.1. Propel統合を最適化する</a></dt>
<dt><a href="#limiting.the.number.of.objects.to.hydrate">18.2.2. ハイドレイトするオブジェクトの数を制限する</a></dt>
<dt><a href="#minimizing.the.number.of.queries.with.joins">18.2.3. Joinでクエリの回数を最小にする</a></dt>
<dt><a href="#avoid.using.temporary.arrays">18.2.4. 一時的な配列の利用を避ける</a></dt>
<dt><a href="#bypassing.the.orm">18.2.5. ORMを回避する</a></dt>
<dt><a href="#speeding.up.the.database">18.2.6. データベースを加速する</a></dt>
</dl></dd>
<dt><a href="#tweaking.the.view">18.3. ビューを調整する</a></dt>
<dd><dl>
<dt><a href="#using.the.fastest.code.fragment">18.3.1. 最速のコードフラグメントを使う</a></dt>
<dt><a href="#speeding.up.the.routing.process">18.3.2. ルーティング処理を加速する</a></dt>
<dt><a href="#skipping.the.template">18.3.3. テンプレートをスキップする</a></dt>
<dt><a href="#restricting.the.default.helpers">18.3.4. デフォルトのヘルパーを制限する</a></dt>
<dt><a href="#compressing.the.response">18.3.5. レスポンスを圧縮する</a></dt>
</dl></dd>
<dt><a href="#tweaking.the.cache">18.4. キャッシュを調整する</a></dt>
<dd><dl>
<dt><a href="#clearing.selective.parts.of.the.cache">18.4.1. キャッシュの一部を選択してクリアする</a></dt>
<dt><a href="#generating.cached.pages">18.4.2. キャッシュページを生成する</a></dt>
<dt><a href="#using.a.database.storage.system.for.caching">18.4.3. キャッシュにデータベースストレージシステムを利用する</a></dt>
<dt><a href="#bypassing.symfony">18.4.4. symfonyを回避する</a></dt>
<dt><a href="#caching.the.result.of.a.function.call">18.4.5. 関数の呼び出し結果をキャッシュする</a></dt>
<dt><a href="#caching.data.in.the.server">18.4.6. データをサーバーにキャッシュする</a></dt>
</dl></dd>
<dt><a href="#deactivating.the.unused.features">18.5. 使わない機能を無効にする</a></dt>
<dt><a href="#optimizing.your.code">18.6. コードを最適化する</a></dt>
<dd><dl>
<dt><a href="#core.compilation">18.6.1. コアコンパイレーション</a></dt>
<dt><a href="#the.sfoptimizer.plugin">18.6.2. sfOptimizerプラグイン</a></dt>
</dl></dd>
<dt><a href="#summary">18.7. まとめ</a></dt>
</dl>
</div>
<a name="tweaking.the.server" id="tweaking.the.server"></a><h2>サーバーを調整する</h2>

<p>よく最適化されたアプリケーションはよく最適化されたサーバーに依存します。symfonyの外部でボトルネックが存在しないことを確認するためにサーバーのパフォーマンスの調整方法の基本を理解しておく必要があります。アプリケーションが不必要に遅くないことを確認するための項目がいくつかあります。</p>

<p><code>php.ini</code>ファイルのなかで<code>magic_quotes_gpc</code>ディレクティブを<code>on</code>にしておくと、アプリケーションが遅くなります。リクエストパラメーター内のすべての引用符をエスケープするようにPHPに伝えるからですが、symfonyはこれらの引用符をあとで体系的にエスケープするので、結果として、時間のロスといくつかのプラットフォームで引用符のエスケーピング問題が起きるだけです。ですので、PHPの設定にアクセスする権限があればこのディレクティブを<code>off</code>にしておいてください。</p>

<p>PHPは最新のリリースであるほど、パフォーマンスがよくなります。PHP 5.2はPHP 5.1よりも速く、PHP 5.1はPHP 5.0よりもはるかに速いです。ですので、パフォーマンスの恩恵を受けるにはPHPを最新のバージョンにアップグレードします。</p>

<p>PHPアクセレータ(たとえばAPC、XCache、eAccelerator)の利用は運用サーバーに対してはほとんど義務です。トレードオフなしでPHPの動作速度を平均で50％速くすることができるからです。PHPの本当の速度を体感するにはアクセレータの拡張機能の1つをインストールしてください。</p>

<p>一方で、運用サーバーでは、XdebugもしくはAPDエクステンションといったデバッグユーティリティは無効にしてください。</p>

<blockquote class="note"><p>
  <code>mod_rewrite</code>拡張機能によって引き起こされるオーバーヘッドについて困ることがあるかもしれませんが無視できます。もちろん、書き換えルールで画像を読み込むことは書き換えルールなしのときよりも遅いですが、減速の規模の桁数はPHPステートメントの実行よりも下です。</p>
</blockquote>

<p>-</p>

<blockquote class="tip"><p>
  1つのサーバーだけでは十分でないとき、別のサーバーを追加すればロードバランス機能を利用できます。<code>uploads/</code>ディレクトリが共有され、セッションに対してデータベースストレージを利用するかぎり、symfonyプロジェクトはロードバランスされたアーキテクチャ内でシームレスに対応します。</p>
</blockquote>

<a name="tweaking.the.model" id="tweaking.the.model"></a><h2>モデルを調整する</h2>

<p>symfonyにおいて、モデルレイヤーはもっとも遅いという評価があります。ベンチマークがこのレイヤーを最適化しなければならないことを示した場合、いくつかの改善方法を利用できます。</p>

<a name="optimizing.propel.integration" id="optimizing.propel.integration"></a><h3>Propel統合を最適化する</h3>

<p>モデルレイヤーの初期化(コアのPropelクラス)は幾分か時間がかかります。いくつかのクラスをロードしてさまざまなオブジェクトをコンストラクトするからです。しかしながら、symfonyがPropelを統合する方法のため、これらの初期化タスクはアクションが実際にモデルを必要とするときのみに起こり、しかもできるかぎり直前に行われます。Propelのクラスは生成モデルのオブジェクトがオートロードされたときのみに初期化されます。このことはモデルを使わないページはモデルレイヤーによるペナルティが課されないことを意味します。</p>

<p>アプリケーション全体がモデルレイヤーの使用を必要としなければ、<code>settings.yml</code>ファイルのなかでレイヤー全体をオフに切り替えることで<code>sfDatabaseManager</code>を初期化しないですみます:</p>

<pre><code>all:
  .settings:
    use_database: off
</code></pre>

<p>生成されたモデルクラス(<code>lib/model/om/</code>)はすでに最適化されています。これらはコメントを含まず、オートロードシステムから恩恵を受けます。ファイルを手動でインクルードする代わりにオートロードに頼ることはクラスが本当に必要な場合だけロードされることを意味します。この場合において、モデルクラスは不要なので、クラスをオートロードすれば実行時間の節約になります。一方で<code>include</code>ステートメントを使う代わりの方法はそうではありません。コメントに関しては、これらは生成されたメソッドの使いかたをドキュメントにしますが、モデルファイルを長くします。結果として遅いディスク上では少々のオーバーヘッドになります。生成されたメソッドの名前はとても明快なので、デフォルトでコメントはオフに切り替えられます。</p>

<p>これら2つの強化方法はsymfony固有のものですが、つぎのように<code>propel.ini</code>ファイルのなかで2つの設定を変更することでPropelのデフォルト設定に戻すことができます:</p>

<pre><code>propel.builder.addIncludes = true   # オートロードシステムに依存する代わりに
                                    # 生成クラスにincludeステートメントを追加する
propel.builder.addComments = true   # 生成クラスにコメントを追加する
</code></pre>

<a name="limiting.the.number.of.objects.to.hydrate" id="limiting.the.number.of.objects.to.hydrate"></a><h3>ハイドレイトするオブジェクトの数を制限する</h3>

<p>オブジェクトを検索するピアクラスのメソッドを利用するとき、クエリはハイドレイティングの処理を行います(クエリの結果の列に基づいてオブジェクトの作成と投入を行う)。たとえば、Propelで<code>article</code>テーブルのすべての列を検索するには、通常つぎのように行います:</p>

<pre class="php"><span class="re0">$articles</span> <span class="sy0">=</span> ArticlePeer<span class="sy0">::</span><span class="me2">doSelect</span><span class="br0">&#40;</span><span class="kw2">new</span> Criteria<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre>

<p>結果の変数<code>$articles</code>は<code>Article</code>クラスのオブジェクト配列です。それぞれのオブジェクトの作成と初期化が行われるので、時間がかかります。これは大きな影響力を持ちます: データベースへの直接のクエリとは逆に、<code>Propel</code>のクエリは返す結果の数に直接比例します。このことはモデルメソッドが特定の数の結果のみを返すために最適化すべきであることを意味します。<code>Criteria</code>オブジェクトによって返されるすべての結果が必要でなければ、<code>setLimit()</code>メソッドと<code>setOffset()</code>メソッドで制限します。たとえば、特定のクエリの10番目から20番目の列のみが必要な場合、リスト18-1のように<code>Criteria</code>オブジェクトを改良します。</p>

<p>リスト18-1 - <code>Criteria</code>オブジェクトによって返される結果の数を制限する</p>

<pre class="php"><span class="re0">$c</span> <span class="sy0">=</span> <span class="kw2">new</span> Criteria<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$c</span><span class="sy0">-&gt;</span><span class="me1">setOffset</span><span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span><span class="sy0">;</span>  <span class="co1">// 返される最初のレコードのオフセット値</span>
<span class="re0">$c</span><span class="sy0">-&gt;</span><span class="me1">setLimit</span><span class="br0">&#40;</span><span class="nu0">10</span><span class="br0">&#41;</span><span class="sy0">;</span>   <span class="co1">// 返されるレコードの数</span>
<span class="re0">$articles</span> <span class="sy0">=</span> ArticlePeer<span class="sy0">::</span><span class="me2">doSelect</span><span class="br0">&#40;</span><span class="re0">$c</span><span class="br0">&#41;</span><span class="sy0">;</span></pre>

<p>これはページャーを利用することで自動化できます。<code>sfPropelPager</code>オブジェクトは任意のページに対して求められたオブジェクトだけをハイドレイトするために自動的にオフセットの値とPropelクエリの制限を処理します。このクラスに関する詳細な情報は<a href="http://www.symfony-project.org/cookbook/1_1/pager">ページャーのドキュメント</a>を参照してください。</p>

<a name="minimizing.the.number.of.queries.with.joins" id="minimizing.the.number.of.queries.with.joins"></a><h3>Joinでクエリの回数を最小にする</h3>

<p>アプリケーションの開発期間において、それぞれのリクエストによって発行されるうデータベースクエリの回数を監視すべきです。Webデバッグツールバーはそれぞれのページに対してクエリの回数を示し、小さなデータベースアイコンをクリックすればこれらのクエリのSQLコードが表示されます。クエリの回数が以上に上昇するのを見かけたら、Joinの利用を考えるべきです。</p>

<p>Joinメソッドを説明するまえに、リスト18-2で示されるように、オブジェクトの配列をループしていて、関連クラスの詳細を検索するためにPropelのゲッターを使うときに何が起きているのかを検討しましょう。この例ではスキーマが<code>author</code>テーブルへの外部キーを持つ<code>article</code>テーブルを記載していることを前提にしています。</p>

<p>リスト18-2 - ループ内で関連クラスの詳細情報を検索する</p>

<pre class="php">// アクションにおいて
$this-&gt;articles = ArticlePeer::doSelect(new Criteria());
&nbsp;
// doSelect()によって発行されたデータベースクエリ
SELECT article.id, article.title, article.author_id, ...
FROM   article
&nbsp;
// テンプレートにおいて
&lt;ul&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">foreach</span> <span class="br0">&#40;</span><span class="re0">$articles</span> <span class="kw1">as</span> <span class="re0">$article</span><span class="br0">&#41;</span><span class="sy0">:</span> <span class="sy1">?&gt;</span>
  &lt;li&gt;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$article</span><span class="sy0">-&gt;</span><span class="me1">getTitle</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>,
    written by <span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$article</span><span class="sy0">-&gt;</span><span class="me1">getAuthor</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">getName</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>&lt;/li&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">endforeach</span><span class="sy0">;</span> <span class="sy1">?&gt;</span>
&lt;/ul&gt;</pre>

<p><code>$articles</code>配列が10のオブジェクトを格納する場合、<code>getAuthor()</code>メソッドは10回呼び出されます。リスト18-3のように、<code>Author</code>クラスの1つのオブジェクトをハイドレイトするためにこのメソッドが呼び出されるたびに、1つのデータベースクエリが順番に実行されます。</p>

<p>リスト18-3 - 外部キーのゲッターは1つのデータベースクエリを発行する</p>

<pre class="php"><span class="co1">// テンプレートにおいて</span>
<span class="re0">$article</span><span class="sy0">-&gt;</span><span class="me1">getAuthor</span><span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp;
<span class="co1">// getAuthor()によって発行されたデータベースクエリ</span>
SELECT author<span class="sy0">.</span>id<span class="sy0">,</span> author<span class="sy0">.</span>name<span class="sy0">,</span> <span class="sy0">...</span>
FROM   author
WHERE  author<span class="sy0">.</span>id <span class="sy0">=</span> ?                <span class="co1">// ? は article.author_id</span></pre>

<p>リスト18-2のページは合計で11のクエリを必要とします: 1つのクエリは<code>Article</code>オブジェクトの配列を作るために、残りの10のクエリは一度に1つの<code>Author</code>オブジェクトを作るために必要です。これは記事と著者の一覧だけを表示するためのたくさんのクエリになります。</p>

<p>SQL文を使っているのであれば、同じクエリで<code>article</code>テーブルと<code>author</code>テーブルのカラムを検索することで多くのクエリの回数を1つだけに減らす方法をご存じでしょう。これがまさに<code>ArticlePeer</code>クラスの<code>doSlectJoinAuthor()</code>メソッドが行うことです。このメソッドは単純な<code>doSelect()</code>呼び出しよりもわずかに複雑なクエリを発行しますが、結果セット内の追加カラムによってPropelは<code>Article</code>オブジェクトと関連する<code>Author</code>オブジェクトの両方をハイドレイトできます。リスト18-4のコードはリスト18-2とまったく同じ結果を示しますが、データベースへの必要なクエリの回数は11回ではなく1回なので速くなります。</p>

<p>リスト18-4 - 同じクエリで記事と著者の詳細情報を検索する</p>

<pre class="php">// アクション内で
$this-&gt;articles = ArticlePeer::doSelectJoinAuthor(new Criteria());
&nbsp;
// doSelectJoinAuthor()によって発行されたデータベースへのクエリ
SELECT article.id, article.title, article.author_id, ...
       author.id, author.name, ...
FROM   article, author
WHERE  article.author_id = author.id
&nbsp;
// テンプレートにおいて(変わらず)
&lt;ul&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">foreach</span> <span class="br0">&#40;</span><span class="re0">$articles</span> <span class="kw1">as</span> <span class="re0">$article</span><span class="br0">&#41;</span><span class="sy0">:</span> <span class="sy1">?&gt;</span>
  &lt;li&gt;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$article</span><span class="sy0">-&gt;</span><span class="me1">getTitle</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>,
    written by <span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$article</span><span class="sy0">-&gt;</span><span class="me1">getAuthor</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">getName</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>&lt;/li&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">endforeach</span><span class="sy0">;</span> <span class="sy1">?&gt;</span>
&lt;/ul&gt;</pre>

<p><code>doSelect()</code>呼び出しと<code>doSelectJoinXXX()</code>メソッドによって返された結果には違いはありません: これらは両方とも(この例では<code>Article</code>クラスの)オブジェクトの同じ配列を返します。違いが現れるのはあとで外部キーのゲッターがこれらのオブジェクトによって利用されるときです。<code>doSelect()</code>メソッドの場合、このメソッドはクエリを発行し、1つのオブジェクトは結果によってハイドレイトされます; <code>doSelectJoinXXX()</code>メソッドの場合、外部オブジェクトはすでに存在しており、クエリが必要ないので処理速度はより速くなります。関連オブジェクトが必要であることを知っている場合、データベースクエリの回数を減らすため、そしてページのパフォーマンスを改善するために<code>DoSelectJoinXXX()</code>メソッドを呼び出します。</p>

<p><code>article</code>テーブルと<code>author</code>テーブル間のリレーションが存在するので、<code>doSelectJoinAuthor()</code>メソッドは<code>propel-build-model</code>を呼び出したときに自動的に生成されます。<code>article</code>テーブルの構造内において、たとえば<code>category</code>テーブルに対してほかの外部キーが存在する場合、リスト18-5で示されるように、生成された<code>BaseArticlePeer</code>クラスはほかのJoinメソッドを持ちます。</p>

<p>リスト18-5 - <code>ArticlePeer</code>クラスに対して利用可能な<code>doSelect</code>メソッド</p>

<pre class="php"><span class="co1">// Articleオブジェクトを検索する</span>
doSelect<span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp;
<span class="co1">// Articleオブジェクトを検索し、関連するAuthorオブジェクトをハイドレイトする</span>
doSelectJoinAuthor<span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp;
<span class="co1">// Articleオブジェクトを検索し、関連するCategoryオブジェクトをハイドレイトする</span>
doSelectJoinCategory<span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp;
<span class="co1">// Articleオブジェクトを検索し、Authorオブジェクト以外の関連レコードをハイドレイトする</span>
doSelectJoinAllExceptAuthor<span class="br0">&#40;</span><span class="br0">&#41;</span>
&nbsp;
<span class="co1">// 同義語</span>
doSelectJoinAll<span class="br0">&#40;</span><span class="br0">&#41;</span></pre>

<p>ピアクラスは<code>doCount()</code>メソッドに対してJoinメソッドも含みます。国際化の対応部分(13章を参照)を持つクラスは<code>doSelectWithI18n()</code>メソッドを提供します。このメソッドは国際化オブジェクト以外はJoinメソッドと同じふるまいをします。モデルクラス内で利用可能なJoinメソッドを見つけるには、<code>lib/model/om/</code>ディレクトリ内で生成されたピアクラスを調べてください。クエリに必要なJoinメソッドが見つからない場合(たとえば、多対多のリレーションのために自動的に生成されたJoinメソッドが存在しない)、あなた自身でメソッドを作りモデルを拡張できます。</p>

<blockquote class="tip"><p>
  もちろん、<code>doSelectJoinXXX()</code>の呼び出しは<code>doSelect()</code>の呼び出しよりも少し遅いので、ハイドレイトされたオブジェクトをあとで利用する場合、これは全体のパフォーマンスを改善するだけです。</p>
</blockquote>

<a name="avoid.using.temporary.arrays" id="avoid.using.temporary.arrays"></a><h3>一時的な配列の利用を避ける</h3>

<p>Propelを利用しているとき、オブジェクトはすでにハイドレイトされており、テンプレートのために一時的な配列を用意する必要はありません。ORMに慣れていない開発者がこの罠に陥ることはよくあります。彼らは文字列もしくは整数の配列を用意したい一方で、テンプレートは既存のオブジェクトの配列に直接依存します。たとえば、テンプレートがデータベース内部に存在する記事のすべてのタイトルの一覧を表示する場合を想像してください。オブジェクト指向のプログラミングをしない開発者はおそらくリスト18-6で示されたようなコードを書くでしょう。</p>

<p>リスト18-6 - 配列がすでに存在する場合アクション内で配列を用意することは無駄である</p>

<pre class="php">// アクション内
$articles = ArticlePeer::doSelect(new Criteria());
$titles = array();
foreach ($articles as $article)
{
  $titles[] = $article-&gt;getTitle();
}
$this-&gt;titles = $titles;
&nbsp;
// テンプレート内
&lt;ul&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">foreach</span> <span class="br0">&#40;</span><span class="re0">$titles</span> <span class="kw1">as</span> <span class="re0">$title</span><span class="br0">&#41;</span><span class="sy0">:</span> <span class="sy1">?&gt;</span>
  &lt;li&gt;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$title</span> <span class="sy1">?&gt;</span>&lt;/li&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">endforeach</span><span class="sy0">;</span> <span class="sy1">?&gt;</span>
&lt;/ul&gt;</pre>

<p>このコードの問題はハイドレイティングがすでに<code>doSelect()</code>の呼び出しによって行われているので(時間がかかります)、配列<code>$titles</code>が余計なものになっていることです。代わりにリスト18-7のようなコードを書けます。配列<code>$titles</code>を作るために費やされた時間が節約されアプリケーションのパフォーマンスが改善されます。</p>

<p>リスト18-7 - オブジェクト配列を使えば一時的な配列を作らずにすむ</p>

<pre class="php">// アクション内
$this-&gt;articles = ArticlePeer::doSelect(new Criteria());
&nbsp;
// テンプレート内
&lt;ul&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">foreach</span> <span class="br0">&#40;</span><span class="re0">$articles</span> <span class="kw1">as</span> <span class="re0">$article</span><span class="br0">&#41;</span><span class="sy0">:</span> <span class="sy1">?&gt;</span>
  &lt;li&gt;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$article</span><span class="sy0">-&gt;</span><span class="me1">getTitle</span><span class="br0">&#40;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span>&lt;/li&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">endforeach</span><span class="sy0">;</span> <span class="sy1">?&gt;</span>
&lt;/ul&gt;</pre>

<p>オブジェクト上でいくつかの処理作業が必要なので一時的な配列を本当に用意する必要があると感じたら、それを行うための正しい方法はこの配列を直接返すモデルクラス内で新しいメソッドを作ることです。たとえば、それぞれの記事に対して記事のタイトルの配列とコメント数が必要な場合、アクションとテンプレートはリスト18-8のようになります。</p>

<p>リスト18-8 - 一時的な配列を用意するためにカスタムメソッドを使う</p>

<pre class="php">// アクション内
$this-&gt;articles = ArticlePeer::getArticleTitlesWithNbComments();
&nbsp;
// テンプレート内
&lt;ul&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">foreach</span> <span class="br0">&#40;</span><span class="re0">$articles</span> <span class="kw1">as</span> <span class="re0">$article</span><span class="br0">&#41;</span><span class="sy0">:</span> <span class="sy1">?&gt;</span>
  &lt;li&gt;<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$article</span><span class="br0">&#91;</span><span class="nu0">0</span><span class="br0">&#93;</span> <span class="sy1">?&gt;</span> (<span class="kw2">&lt;?php</span> <span class="kw1">echo</span> <span class="re0">$article</span><span class="br0">&#91;</span><span class="nu0">1</span><span class="br0">&#93;</span> <span class="sy1">?&gt;</span> comments)&lt;/li&gt;
<span class="kw2">&lt;?php</span> <span class="kw1">endforeach</span><span class="sy0">;</span> <span class="sy1">?&gt;</span>
&lt;/ul&gt;</pre>

<p>モデル内で速い処理である<code>getArticleTitlesWithNbComments()</code>メソッドを作るのはあなた次第です。たとえば、オブジェクトリレーショナルマッピングとデータベース抽象レイヤー全体を回避することによって行われます。</p>

<a name="bypassing.the.orm" id="bypassing.the.orm"></a><h3>ORMを回避する</h3>

<p>以前の例のように、オブジェクトが不要でさまざまなテーブルからいくつかのカラムのみが必要な場合、モデル内でORMレイヤーを完全に回避する限定的なメソッドを作成できます。たとえば、Creoleを利用してデータベースを直接呼び出して特製の配列を返します。リスト18-9はこのアイディアを説明しています。</p>

<p>リスト18-9 - 最適化されたモデルメソッドのためにCreoleで直接データベースにアクセスする(<code>lib/model/ArticlePeer.php</code>)</p>

<pre class="php"><span class="kw2">class</span> ArticlePeer <span class="kw2">extends</span> BaseArticlePeer
<span class="br0">&#123;</span>
  <span class="kw2">public</span> static <span class="kw2">function</span> getArticleTitlesWithNbComments<span class="br0">&#40;</span><span class="br0">&#41;</span>
  <span class="br0">&#123;</span>
    <span class="re0">$connection</span> <span class="sy0">=</span> Propel<span class="sy0">::</span><span class="me2">getConnection</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$query</span> <span class="sy0">=</span> <span class="st_h">'SELECT %s as title, COUNT(%s) AS nb FROM %s LEFT JOIN %s ON %s = %sGROUP BY %s'</span><span class="sy0">;</span>
    <span class="re0">$query</span> <span class="sy0">=</span> <span class="kw3">sprintf</span><span class="br0">&#40;</span><span class="re0">$query</span><span class="sy0">,</span>
      ArticlePeer<span class="sy0">::</span><span class="me2">TITLE</span><span class="sy0">,</span> CommentPeer<span class="sy0">::</span><span class="me2">ID</span><span class="sy0">,</span>
      ArticlePeer<span class="sy0">::</span><span class="me2">TABLE_NAME</span><span class="sy0">,</span> CommentPeer<span class="sy0">::</span><span class="me2">TABLE_NAME</span><span class="sy0">,</span>
      ArticlePeer<span class="sy0">::</span><span class="me2">ID</span><span class="sy0">,</span> CommentPeer<span class="sy0">::</span><span class="me2">ARTICLE_ID</span><span class="sy0">,</span>
      ArticlePeer<span class="sy0">::</span><span class="me2">ID</span>
    <span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$statement</span> <span class="sy0">=</span> <span class="re0">$connection</span><span class="sy0">-&gt;</span><span class="me1">prepareStatement</span><span class="br0">&#40;</span><span class="re0">$query</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$resultset</span> <span class="sy0">=</span> <span class="re0">$statement</span><span class="sy0">-&gt;</span><span class="me1">executeQuery</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="re0">$results</span> <span class="sy0">=</span> <span class="kw3">array</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="kw1">while</span> <span class="br0">&#40;</span><span class="re0">$resultset</span><span class="sy0">-&gt;</span><span class="me1">next</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
    <span class="br0">&#123;</span>
      <span class="re0">$results</span><span class="br0">&#91;</span><span class="br0">&#93;</span> <span class="sy0">=</span> <span class="kw3">array</span><span class="br0">&#40;</span><span class="re0">$resultset</span><span class="sy0">-&gt;</span><span class="me1">getString</span><span class="br0">&#40;</span><span class="st_h">'title'</span><span class="br0">&#41;</span><span class="sy0">,</span> <span class="re0">$resultset</span><span class="sy0">-&gt;</span><span class="me1">getInt</span><span class="br0">&#40;</span><span class="st_h">'nb'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
    <span class="br0">&#125;</span>
&nbsp;
    <span class="kw1">return</span> <span class="re0">$results</span><span class="sy0">;</span>
  <span class="br0">&#125;</span>
<span class="br0">&#125;</span></pre>

<p>この種のメソッドを作り始めるとき、それぞれのアクションに対して1つのカスタムメソッドを書くことで終わるので、階層分離の恩恵が失われます。データベースの独立性も失われることは言うまでもありません。</p>

<blockquote class="tip"><p>
  Propelがモデルレイヤーに適していない場合、クエリを手作業で書くまえにほかのORMを使うことを考えてください。たとえば、PhpDoctrineによるインターフェイスのための<code>sfDoctrine</code>プラグインを確認してください。加えて、Creole以外にもデータベースに直接アクセスするほかのデータベース抽象化レイヤーも利用できます。PHP 5.1において、PDOがPHPにバンドルされ、Creoleより速い代替機能を提供します。</p>
</blockquote>

<a name="speeding.up.the.database" id="speeding.up.the.database"></a><h3>データベースを加速する</h3>

<p>symfonyを利用するかかかわらず適用できるデータベース固有の最適化テクニックが多く存在します。このセクションは手短にもっとも共通のデータベース最適化戦略の要点をまとめていますが、モデルレイヤーを最大限利用するにはデータベースエンジンと管理方法に関して詳しい知識が必要です。</p>

<blockquote class="tip"><p>
  Webデバッグツールバーはページ単位でそれぞれのクエリのために費やされた時間を表示し、本当にパフォーマンスが改善されたのかを判断するためにすべての調整をモニタリングされることを覚えておいてください。</p>
</blockquote>

<p>テーブルクエリは主キーではないカラムを基にすることがよくあります。このようなクエリの速さを改善するために、データベーススキーマのなかでインデックスを定義します。単独のカラムインデックスを追加するには、リスト18-10のように、<code>index: true</code>プロパティをカラムの定義に追加します。</p>

<p>リスト18-10 - 単独のカラムインデックスを追加する(<code>config/schema.yml</code>)</p>

<pre><code>propel:
  article:
    id:
    author_id:
    title: { type: varchar(100), index: true }
</code></pre>

<p>古典的なインデックスの代わりにユニークインデックスを定義するために代替の<code>index: unique</code>構文を利用できます。<code>schema.yml</code>ファイルで複数のカラムインデックスを定義することもできます(インデックスの構文に関する詳細な情報は8章を参照)。この方法はしばし複雑なクエリを加速するのによいのでよく熟慮すべきです。</p>

<p>インデックスをスキーマに追加したあとで、<code>ADD INDEX</code>クエリを直接データベースに発行するか、<code>propel-build-all</code>コマンドを呼び出せばデータベース自身が同じことを行います(テーブル構造をリビルドするだけでなく、既存のすべてのデータを削除します)。</p>

<blockquote class="tip"><p>
  インデックスを作成することで<code>SELECT</code>クエリは速くなりますが、<code>INSERT</code>、<code>UPDATE</code>、と<code>DELETE</code>が遅くなる傾向にあります。また、データベースエンジンは1つのクエリごとに1つのインデックスを使用し、内部の経験則に基づいてそれぞれのクエリのために使われるインデックスを推測します。インデックスを追加するとパフォーマンスの加速に関してがっかりな結果になることも時折あるので、かならず改善結果を測定してください。</p>
</blockquote>

<p>指定されないかぎり、symfonyにおいてそれぞれのリクエストは単独のデータベース接続方法を利用し、接続はリクエストの終了時点で閉じられます。リスト18-11で示されるように、<code>databases.yml</code>ファイルのなかで<code>persistent: true</code>を設定することで、クエリの間に開いた状態を保つデータベースの接続プールを利用するための永続的なデータベース接続を有効にできます。</p>

<p>リスト18-11 - データベースの永続的な接続サポートを有効にする(<code>config/databases.yml</code>)</p>

<pre><code>prod:
  propel:
    class:          sfPropelDatabase
    param:
      persistent:   true
      dsn:          mysql://login:passwd@localhost/blog
</code></pre>

<p>これがデータベース全体のパフォーマンスを改善をするのかどうかは多くの要素によります。この主題に関するドキュメントはインターネット上で豊富にあります。利点を検証するためにこの設定を変更する前あとでアプリケーションのパフォーマンスをかならずベンチマークしてください。</p>

<blockquote class="sidebar"><p class="title">
  MySQL固有のティップス</p>
  
  <p><code>my.cnf</code>ファイルのなかで見つかる、MySQLのコンフィギュレーションの多くの設定は、データベースパフォーマンスを変えることがあります。この主題についてはオンラインドキュメント(<a href="http://dev.mysql.com/doc/refman/5.1/ja/option-files.html">http://dev.mysql.com/doc/refman/5.1/ja/option-files.html</a>)をかならず読んでください。</p>
  
  <p>MySQLによって提供されたツールの1つはスロークエリログです。実行するのに<code>long_query_time</code>秒よりも時間がかかるすべてのSQLステートメント(<code>my.cnf</code>で変更できる設定)は手作業で構文解析するのがとても難しいファイルに記録されますが、<code>mysqldumpslow</code>コマンドはわかりやすいようにまとめします。これは最適化が必要なクエリを検出するためのすばらしいツールです。</p>
</blockquote>

<a name="tweaking.the.view" id="tweaking.the.view"></a><h2>ビューを調整する</h2>

<p>ビューレイヤーを設計し実装する方法によって、小さな減速もしくは加速が起きることにお気づきかもしれません。このセクションは代わりの方法とトレードオフについて説明します。</p>

<a name="using.the.fastest.code.fragment" id="using.the.fastest.code.fragment"></a><h3>最速のコードフラグメントを使う</h3>

<p>キャッシュシステムを利用しない場合、<code>include_component()</code>ヘルパーが<code>include_partial()</code>ヘルパーよりも遅く、<code>include_partial()</code>ヘルパーは単純なPHPの<code>include</code>ステートメントよりも遅いことは認識すべきです。symfonyはコンポーネントをインクルードするために部分テンプレートと<code>sfComponent</code>クラスのオブジェクトを含むビューをインスタンス化するからです。ファイルをインクルードするために必要なもの以上の小さなオーバーヘッドは累積されます。</p>

<p>しかしながら、多くの部分テンプレートもしくはコンポーネントをテンプレート内部に含まないかぎり、このオーバーヘッドは重要ではありません。リストもしくはテーブル内、<code>foreach</code>ステートメント内で<code>include_partial()</code>ヘルパーを呼び出すたびにオーバーヘッドが起こる可能性があります。膨大な数の部分テンプレートもしくはコンポーネントのインクルードがパフォーマンスに重大な影響を与えるとき、キャッシュを考えるか(12章を参照)、キャッシュが選択肢になければ、単純な<code>include</code>ステートメントに切り替えます。</p>

<p>スロットとコンポーネントスロットに関して、パフォーマンスの違いを知覚できます。スロットを設定してインクルードするために必要な処理時間は無視できます。これは変数のインスタンス化と同じことです。コンポーネントスロットはビューの設定に依存し、これらを機能させるためにインスタンス化される必要があります。しかしながら、コンポーネントスロットはテンプレートから呼び出すことから個別にキャッシュできるのに対して、スロットはそれらを含むテンプレート内でつねにキャッシュされます。</p>

<a name="speeding.up.the.routing.process" id="speeding.up.the.routing.process"></a><h3>ルーティング処理を加速する</h3>

<p>9章で説明されたように、テンプレート内部で<code>link</code>ヘルパーへのすべての呼び出しはルーティングシステムに内部URIを外部URLに処理することを求めます。これはURIと<code>routing.yml</code>ファイルのパターンの間のマッチを見つけることによって行われます。symfonyはこれを簡単に実行します: 任意のURIが最初のルールにマッチするか試し、マッチしない場合、つぎのルールで試すことを行います。すべてのテストは正規表現を含むので、これはとても時間のかかる処理です。</p>

<p>簡単な次善策があります: モジュール/アクションの組み合わせの代わりにルール名を使います。これはどのルールを使うのかsymfonyに伝えるので、ルーティングシステムは以前のすべてのルールにマッチさせる処理を行わずにすみます。</p>

<p>具体的には、<code>routing.yml</code>ファイルで定義されたつぎのルーティングルールを考えてください:</p>

<pre><code>article_by_id:
  url:          /article/:id
  param:        { module: article, action: read }
</code></pre>

<p>ハイパーリンクの出力の代わりにつぎの方法で:</p>

<pre class="php"><span class="kw2">&lt;?php</span> <span class="kw1">echo</span> link_to<span class="br0">&#40;</span><span class="st_h">'my article'</span><span class="sy0">,</span> <span class="st_h">'article/read?id='</span><span class="sy0">.</span><span class="re0">$article</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span></pre>

<p>最速のバージョンを使います:</p>

<pre class="php"><span class="kw2">&lt;?php</span> <span class="kw1">echo</span> link_to<span class="br0">&#40;</span><span class="st_h">'my article'</span><span class="sy0">,</span> <span class="st_h">'@article_by_id?id='</span><span class="sy0">.</span><span class="re0">$article</span><span class="sy0">-&gt;</span><span class="me1">getId</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="br0">&#41;</span> <span class="sy1">?&gt;</span></pre>

<p>ページがたくさんのルーティングが行われたハイパーリンクを含むときに違いがわかるようになります。</p>

<a name="skipping.the.template" id="skipping.the.template"></a><h3>テンプレートをスキップする</h3>

<p>通常、レスポンスはヘッダーと内容の一式で構成されます。レスポンスのなかには内容を必要としないものがあります。たとえば、ページの異なる部分を更新するJavaScriptを提供するために、Ajaxインタラクションはサーバーからデータの少しの部分だけ必要です。この種の短いレスポンスのために、ヘッダーだけのセットを送るほうが少し速いです。11章で検討したように、アクションはJSONヘッダーだけを返すことができます。リスト18-12は11章からの例を再現します。</p>

<p>リスト18-12 - JSONヘッダーを返すアクションの例</p>

<pre class="php"><span class="kw2">public</span> <span class="kw2">function</span> executeRefresh<span class="br0">&#40;</span><span class="br0">&#41;</span>
<span class="br0">&#123;</span>
  <span class="re0">$output</span> <span class="sy0">=</span> <span class="st_h">'{&quot;title&quot;:&quot;My basic letter&quot;,&quot;name&quot;:&quot;Mr Brown&quot;}'</span><span class="sy0">;</span>
  <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getResponse</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">setHttpHeader</span><span class="br0">&#40;</span><span class="st0">&quot;X-JSON&quot;</span><span class="sy0">,</span> <span class="st_h">'('</span><span class="sy0">.</span><span class="re0">$output</span><span class="sy0">.</span><span class="st_h">')'</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
  <span class="kw1">return</span> sfView<span class="sy0">::</span><span class="me2">HEADER_ONLY</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre>

<p>このコードはテンプレートとレイアウト、そして一度だけ送信されるレスポンスをスキップします。これはヘッダーだけを含むので、もっと軽量でユーザーに送信するために必要な時間はより短くなります。</p>

<p>6章ではテキストの内容をアクションから直接返すことでテンプレートをスキップする別の方法を説明しました。これはMVC分離の原則を破ることになりますが、アクションの反応がとても速くなります。例としてリスト18-13をご覧ください。</p>

<p>リスト18-13 - テキストの内容を直接返すアクションの例</p>

<pre class="php"><span class="kw2">public</span> <span class="kw2">function</span> executeFastAction<span class="br0">&#40;</span><span class="br0">&#41;</span>
<span class="br0">&#123;</span>
  <span class="kw1">return</span> <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">renderText</span><span class="br0">&#40;</span><span class="st0">&quot;&lt;html&gt;&lt;body&gt;Hello, World!&lt;/body&gt;&lt;/html&gt;&quot;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre>

<a name="restricting.the.default.helpers" id="restricting.the.default.helpers"></a><h3>デフォルトのヘルパーを制限する</h3>

<p>標準のヘルパーグループ(<code>Partial</code>、<code>Cache</code>、と<code>Form</code>)はすべてのリクエストごとにロードされます。これらのいくつかを使わないことがわかっているのであれば、標準のヘルパーグループのリストから1つのヘルパーグループを除外すればヘルパーファイルの解析を行わずにすむようになります。とりわけ、<code>Form</code>ヘルパーグループはデフォルトで含まれていますが、サイズが大きいのでフォームなしのページの表示が重くなります。<code>Form</code>ヘルパーを除外するために<code>settings.yml</code>ファイルのなかで<code>standard_helpers</code>設定を編集するのはよいアイディアかもしれません:</p>

<pre><code>all:
  .settings:
    standard_helpers: [Partial, Cache]    # Formが除外された
</code></pre>

<p>トレードオフは<code>use_helper('Form')</code>ヘルパーで<code>Form</code>ヘルパーグループを利用するテンプレートごとにこのヘルパーグループを宣言しなければならないことです。</p>

<a name="compressing.the.response" id="compressing.the.response"></a><h3>レスポンスを圧縮する</h3>

<p>symfonyはユーザーにレスポンスを送るまえにレスポンスを圧縮します。この機能はPHPのzlibモジュールによるものです。<code>settings.yml</code>ファイルでこの機能を無効にすればそれぞれのリクエストに対するCPUの時間を少し節約できます:</p>

<pre><code>all:
  .settings:
    compressed: off
</code></pre>

<p>CPUのゲインは帯域の損失によってバランスが保たれるので、この変更によってすべての設定でパフォーマンスが向上するわけではないので注意してください。</p>

<blockquote class="tip"><p>
  PHPでzip圧縮を無効にする場合、サーバーレベルで有効にできます。Apacheは圧縮のための独自の拡張機能を持ちます。</p>
</blockquote>

<a name="tweaking.the.cache" id="tweaking.the.cache"></a><h2>キャッシュを調整する</h2>

<p>12章でレスポンスの部分もしくはそのすべてをキャッシュする方法を説明しました。レスポンスのキャッシュは主要なパフォーマンス改善につながるので、最適化のには最初に考慮すべきことの1つです。キャッシュシステムを最大限活用したいのであれば、このセクションを読めば、おそらくあなたが考えていなかったいくつのトリックがわかります。</p>

<a name="clearing.selective.parts.of.the.cache" id="clearing.selective.parts.of.the.cache"></a><h3>キャッシュの一部を選択してクリアする</h3>

<p>アプリケーションの開発期間において、さまざまな状況でキャッシュをクリアしなければなりません:</p>

<ul>
<li>新しいクラスを作るとき: 非開発環境においてクラスをオートロードディレクトリ(プロジェクトの<code>lib/</code>フォルダーの1つ)に追加するだけではsymfonyはそれを見つけられません。symfonyが<code>autoload.yml</code>ファイルのディレクトリのすべてを再び閲覧して新しいクラスを含めてオートロード可能なクラスの位置を参照できるように、オートロードのコンフィギュレーションキャッシュをクリアしなければなりません。</li>
<li>運用サーバーで設定を変更するとき: 運用サーバーにおいて設定は最初のリクエストされた期間のみ解析されます。それ以降のリクエストは代わりにキャッシュを利用します。ですのでキャッシュバージョンのファイルをクリアしないかぎり運用環境(もしくはデバッグ機能がオフの環境)内の設定を変更しても効果はありません。</li>
<li>テンプレートキャッシュが有効である環境においてテンプレートを修正するとき: 運用環境において既存のテンプレートの代わりにキャッシュされた有効なテンプレートがつねに使われるので、テンプレートの変更はテンプレートのキャッシュがクリアもしくは期限切れになるまで無視されます。</li>
<li><code>project:deploy</code>コマンドでアプリケーションを更新するとき: この場合通常は3つの以前の修正をカバーします。</li>
</ul>

<p>キャッシュ全体のクリアに関連する問題は、コンフィギュレーションキャッシュが再生成される必要があるため、つぎのリクエストの処理時間がとても長くなることです。加えて、修正されなかったテンプレートも同じようにキャッシュからクリアされ、以前のリクエストの恩恵を失います。</p>

<p>このことは本当に再生成する必要のあるキャッシュファイルだけをクリアすることがよいアイディアであることを意味します。リスト18-14で示されるように、クリアするキャッシュファイルの部分集合を定義するには<code>cache:clear</code>タスクのオプションを使います。</p>

<p>リスト18-14 - キャッシュの選択した部分のみをクリアする</p>

<pre><code>// frontendアプリケーションのキャッシュのみをクリアする
&gt; php symfony cache:clear frontend

// frontendアプリケーションのHTMLキャッシュのみをクリアする
&gt; php symfony cache:clear frontend template

// frontendアプリケーションのコンフィギュレーションキャッシュのみをクリアする
&gt; php symfony cache:clear frontend config
</code></pre>

<p>12章で説明されたように、<code>cache/</code>ディレクトリのファイルを手作業で削除する、もしくは<code>$cacheManger-&gt;remove()メソッド</code>でアクションから選択したテンプレートキャッシュをクリアすることもできます。</p>

<p>これらすべてのテクニックは前のリストに示された変更によるネガティブなパフォーマンスの影響を最小にします。</p>

<blockquote class="tip"><p>
  symfonyをアップグレードするとき、手動による介入を行わなくても、キャッシュは自動的にクリアされます(<code>settings.yml</code>のなかで<code>check_symfony_version</code>パラメーターを<code>true</code>に設定している場合)。</p>
</blockquote>

<a name="generating.cached.pages" id="generating.cached.pages"></a><h3>キャッシュページを生成する</h3>

<p>新しいアプリケーションを運用サーバーにデプロイしたとき、テンプレートキャッシュは空です。キャッシュに設置されたページを一度訪問するユーザーを待たなければなりません。クリティカルな開発において、ページ処理のオーバーヘッドは受け入れられるものではなく、最初のリクエストが発行されると同時にキャッシュの利点を利用できなければなりません。</p>

<p>解決方法はテンプレートキャッシュを生成するためにステージング(staging)環境(設定は運用環境と似ている)でアプリケーションのページを自動的にブラウジングして、キャッシュを持つアプリケーションを運用サーバーに転送することです。</p>

<p>ページを自動的にブラウジングするための選択肢の1つは ブラウザーで外部URLのリストを通して見るシェルスクリプト(たとえばcurl)を作成することです。しかし、より速く優れた解決方法があります: <code>sfTestBrowser</code>オブジェクトを利用するsymfonyバッチです。これはすでに15章で検討されました。これはPHPで書かれた内部ブラウザーです(そして機能テストのために<code>sfTestBrowser</code>によって使われます)。これは外部URLを取得しレスポンスを返しますが、興味深いことは通常のブラウザーのようにテンプレートキャッシュの生成機能を実行させることです。これはsymfonyを一度だけ初期化してHTTP転送レイヤーを通さないので、この方法ははるかに速いです。</p>

<p>リスト18-15はステージング環境においてテンプレートキャッシュファイルを生成するために使われるバッチスクリプトの例を示しています。このバッチは<code>php batch/generate_cache.php</code>を呼び出すことで実行されます。</p>

<p>リスト18-15 - テンプレートキャッシュを生成する(<code>batch/generate_cache.php</code>)</p>

<pre class="php"><span class="kw1">require_once</span><span class="br0">&#40;</span><span class="kw3">dirname</span><span class="br0">&#40;</span><span class="kw4">__FILE__</span><span class="br0">&#41;</span><span class="sy0">.</span><span class="st_h">'/../config/ProjectConfiguration.class.php'</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$configuration</span> <span class="sy0">=</span> ProjectConfiguration<span class="sy0">::</span><span class="me2">getApplicationConfiguration</span><span class="br0">&#40;</span><span class="st_h">'frontend'</span><span class="sy0">,</span> <span class="st_h">'staging'</span><span class="sy0">,</span> <span class="kw4">false</span><span class="br0">&#41;</span><span class="sy0">;</span>
sfContext<span class="sy0">::</span><span class="me2">createInstance</span><span class="br0">&#40;</span><span class="re0">$configuration</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="co1">// ブラウジングするURLの配列</span>
<span class="re0">$uris</span> <span class="sy0">=</span> <span class="kw3">array</span><span class="br0">&#40;</span>
  <span class="st_h">'/foo/index'</span><span class="sy0">,</span>
  <span class="st_h">'/foo/bar/id/1'</span><span class="sy0">,</span>
  <span class="st_h">'/foo/bar/id/2'</span><span class="sy0">,</span>
  <span class="sy0">...</span>
<span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="re0">$b</span> <span class="sy0">=</span> <span class="kw2">new</span> sfBrowser<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="kw1">foreach</span> <span class="br0">&#40;</span><span class="re0">$uris</span> <span class="kw1">as</span> <span class="re0">$uri</span><span class="br0">&#41;</span>
<span class="br0">&#123;</span>
  <span class="re0">$b</span><span class="sy0">-&gt;</span><span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$uri</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre>

<a name="using.a.database.storage.system.for.caching" id="using.a.database.storage.system.for.caching"></a><h3>キャッシュにデータベースストレージシステムを利用する</h3>

<p>symfonyにおいてテンプレートキャッシュ用のデフォルトストレージシステムはファイルシステムです: HTMLのフラグメントもしくはシリアライズされたレスポンスオブジェクトはプロジェクトの<code>cache/</code>ディレクトリに保存されます。symfonyはキャッシュを保存するための代わりの方法を提案します: SQLiteデータベースです。このデータベースはPHPがネイティブでとても効果的にクエリを行う方法を知っているシンプルなファイルです。</p>

<p>テンプレートキャッシュに対してファイルシステムストレージの代わりに<code>SQLite</code>ストレージを使うようにsymfonyに伝えるには、<code>factories.yml</code>ファイルを開き、<code>view_cache</code>エントリーをつぎのように編集します:</p>

<pre><code>view_cache:
  class: sfSQLiteCache
  param:
    database: %SF_TEMPLATE_CACHE_DIR%/cache.db
</code></pre>

<p>テンプレートキャッシュのためにSQLiteストレージを利用する利点はキャッシュ要素の数が多いときに読み込みと書き込みのオペレーションが速くなることです。アプリケーションがキャッシュを大量に使うとき、テンプレートのキャッシュファイルがファイル構造の深い部分に散乱してしまいます; この場合、SQLiteストレージに切り替えることでパフォーマンスが増加します。加えて、ファイルシステムストレージ上のキャッシュをクリアすると大量のファイルをディスクから削除することが必要になることがあります; このオペレーションは数秒続くので、この間はオペレーションを利用できません。SQLiteストレージシステムによって、キャッシュのクリア処理は単独のファイルオペレーション、SQLiteデータベースファイルの削除ですみます。現在保存されているキャッシュ要素の数がなんであれ、オペレーションは即座に行われます。</p>

<a name="bypassing.symfony" id="bypassing.symfony"></a><h3>symfonyを回避する</h3>

<p>おそらくsymfonyを加速するベストな方法はsymfony自身を完全に回避することです・・・これは一部冗談が入っています。ページのなかには変更されないものがあり、これらはリクエストごとにsymfonyによって再処理される必要はありません。これらのページの配信を加速するためにテンプレートキャッシュはすでに存在しますが、まだsymfonyに依存しています。</p>

<p>いくつかのページに関しては12章で説明されたトリックを組み合わせることでsymfonyを完全に回避できます。最初のトリックはプロキシとクライアントブラウザーがそれら自身でページをキャッシュするように求めるためにHTTP 1.1のヘッダーを利用する方法で、それらはつぎにページが必要なときにページを再リクエストしません。2番目のトリックはスーパーファーストキャッシュ(<code>sfSuperCachePlugin</code>プラグインによって自動化される)です。Apacheがリクエストをsymfonyへ渡すまえに最初にキャッシュを探すように、これは<code>web/</code>ディレクトリ内のレスポンスのコピーの保存と書き換えルールの修正から構成されます。</p>

<p>これらの両方の方法はとても効果的なので、静的なページに適用する場合でもページを処理する負担をsymfonyからとり除き、サーバーは複雑なリクエストを十分に対処できるようになります。</p>

<a name="caching.the.result.of.a.function.call" id="caching.the.result.of.a.function.call"></a><h3>関数の呼び出し結果をキャッシュする</h3>

<p>関数が文脈依存な値もしくはランダム性に依存しない場合、その関数を同じパラメーターで2回呼び出すと同じ値が戻ります。このことは最初に結果を保存していれば2番目の呼び出しは十分に回避できたことを意味します。<code>sfFunctionCache</code>クラスが担う仕事はまさにこれです。このクラスの<code>call()</code>メソッドは引数としてcallableとパラメーターの配列を必要とします。呼び出されたとき、 このメソッドはすべての引数でmd5ハッシュを作りキャッシュのなかでこのハッシュで名づけられたキーを探します。ファイルが見つかれば、関数はファイルに保存された結果を返します。そうでなければ、<code>sfFunctionCache</code>クラスが関数を実行し、結果をキャッシュに保存し、それを返します。リスト18-16の2番目の関数の実行は最初のものより速いです。</p>

<p>リスト18-16 - 関数の結果をキャッシュする</p>

<pre class="php"><span class="re0">$cache</span> <span class="sy0">=</span> <span class="kw2">new</span> sfFileCache<span class="br0">&#40;</span><span class="kw3">array</span><span class="br0">&#40;</span><span class="st_h">'cache_dir'</span> <span class="sy0">=&gt;</span> sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'sf_cache_dir'</span><span class="br0">&#41;</span><span class="sy0">.</span><span class="st_h">'/function'</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$fc</span> <span class="sy0">=</span> <span class="kw2">new</span> sfFunctionCache<span class="br0">&#40;</span><span class="re0">$cache</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$result1</span> <span class="sy0">=</span> <span class="re0">$fc</span><span class="sy0">-&gt;</span><span class="me1">call</span><span class="br0">&#40;</span><span class="st_h">'cos'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span>M_PI<span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="re0">$result2</span> <span class="sy0">=</span> <span class="re0">$fc</span><span class="sy0">-&gt;</span><span class="me1">call</span><span class="br0">&#40;</span><span class="st_h">'preg_replace'</span><span class="sy0">,</span> <span class="kw3">array</span><span class="br0">&#40;</span><span class="st_h">'/\s\s+/'</span><span class="sy0">,</span> <span class="st_h">' '</span><span class="sy0">,</span> <span class="re0">$input</span><span class="br0">&#41;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre>

<p><code>sfFunctionCache</code>のコンストラクターはキャッシュオブジェクトを必要とします。<code>call()</code>メソッドの最初の引数は呼び出し可能でなければならないので、関数名、クラス名と静的メソッド名の配列、もしくはオブジェクト名とpublicなメソッド名の配列でなければなりません。<code>call()</code>メソッドの別の引数に関して、これはcallableに渡される引数の配列です。</p>

<blockquote class="caution"><p>
  例に関してキャッシュオブジェクトに基づいてファイルを使う場合、<code>cache/</code>ディレクトリの元にキャッシュディレクトリを置くのがベターです。<code>cache:clear</code>タスクで自動的にキャッシュが一掃されるからです。関数キャッシュをほかのどこかに保存する場合、コマンドラインを通してキャッシュをクリアするときこれは自動的にクリアされません。</p>
</blockquote>

<a name="caching.data.in.the.server" id="caching.data.in.the.server"></a><h3>データをサーバーにキャッシュする</h3>

<p>PHPアクセレータはデータをメモリに保存する特別な機能を提供するので複数のリクエストにまたがってデータを再利用できます。問題はこれらの機能が異なる構文を持ち、それぞれがこのタスクを実行するための独自の方法を持つことです。symfonyのキャッシュクラスはこれらすべての違いを抽出してどんなアクセレータであれ連携します。リスト18-17で構文をご覧ください。</p>

<p>リスト18-17 - データをキャッシュするためにPHPアクセレータを利用する</p>

<pre class="php"><span class="re0">$cache</span> <span class="sy0">=</span> <span class="kw2">new</span> sfAPCCache<span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="co1">// データをキャッシュに保存する</span>
<span class="re0">$cache</span><span class="sy0">-&gt;</span><span class="me1">set</span><span class="br0">&#40;</span><span class="re0">$name</span><span class="sy0">,</span> <span class="re0">$value</span><span class="sy0">,</span> <span class="re0">$lifetime</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="co1">// データを読みとる</span>
<span class="re0">$value</span> <span class="sy0">=</span> <span class="re0">$cache</span><span class="sy0">-&gt;</span><span class="me1">get</span><span class="br0">&#40;</span><span class="re0">$name</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="co1">// データのピースがキャッシュのなかに存在するかチェックする</span>
<span class="re0">$value_exists</span> <span class="sy0">=</span> <span class="re0">$cache</span><span class="sy0">-&gt;</span><span class="me1">has</span><span class="br0">&#40;</span><span class="re0">$name</span><span class="br0">&#41;</span><span class="sy0">;</span>
&nbsp;
<span class="co1">// キャッシュをクリアする</span>
<span class="re0">$cache</span><span class="sy0">-&gt;</span><span class="me1">clear</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">;</span></pre>

<p>キャッシュ機能が動作しなかった場合<code>set()</code>メソッドは<code>false</code>を返します。キャッシュされた値は何でもなります(文字列、配列、オブジェクト); <code>sfProcessCache</code>クラスはシリアル化を処理します。求められた変数がキャッシュ内に存在しなかった場合<code>get()</code>メソッドは<code>null</code>を返します。</p>

<blockquote class="tip"><p>
  メモリキャッシュをより研究したい場合、<code>sfMemcacheCache</code>クラスをかならず調べてください。これはほかのキャッシュクラスと同じインターフェイスを提供しロードバランスされたアプリケーション上のデータベースのロードを減らす助けを行うことができます。</p>
</blockquote>

<a name="deactivating.the.unused.features" id="deactivating.the.unused.features"></a><h2>使わない機能を無効にする</h2>

<p>symfonyのデフォルト設定ではWebアプリケーションのもっとも共通する機能を有効にしています。しかしながら、これらすべてが必要ではない場合、それぞれのリクエストごとに初期化にかかる時間を節約するためにこれらを無効にできます。</p>

<p>たとえば、アプリケーションがセッションのメカニズムを利用しない、もしくは手動でセッションの扱いを始めたい場合、リスト18-19のように、<code>factories.yml</code>ファイルの<code>storage</code>キーのなかの<code>auto_start</code>設定を<code>false</code>に変えます。</p>

<p>リスト18-19 - セッションをオフにする(<code>frontend/config/factories.yml</code>)</p>

<pre><code>all:
  storage:
    class: sfSessionStorage
    param:
      auto_start: false
</code></pre>

<p>同じことがデータベース機能にあてはまります(この章の前の方の"モデルを調整する"のセクションで説明されています)。アプリケーションがデータベースを利用しないのであれば、小さなパフォーマンスのゲインを得るために、今回は<code>settings.yml</code>ファイルでこの機能を無効にします(リスト18-20を参照)。</p>

<p>リスト18-20 - データベースの機能を無効にする(<code>frontend/cofig/settings.yml</code>)</p>

<pre><code>all:
  .settings:
    use_database:      off    # データベースとモデルの機能
</code></pre>

<p>セキュリティ機能(6章を参照)に関しては、リスト18-21で示されるように、<code>filters.yml</code>ファイル内で無効にできます。</p>

<p>リスト18-21 - セキュリティ機能を無効にする(<code>frontend/config/filters.yml</code>)</p>

<pre><code>rendering: ~
security:
  enabled: off

# 一般的に、ここの独自のフィルターを追加したい場合

cache:     ~
common:    ~
execution: ~
</code></pre>

<p>いくつかの機能は開発環境のときだけ便利な機能なので運用環境ではこれらを有効にしないほうがいいでしょう。symfonyの運用環境のパフォーマンスは本当に最適化されているので、この方法はすでにデフォルトであてはまります。パフォーマンスが影響を与える開発機能のなかで、デバッグモードはもっとも厳しいものです。symfonyのロギング機能に関して、運用環境ではこの機能もデフォルトでオフにされます。</p>

<p>ロギング機能を無効にしていて、開発環境だけで起きるわけでない問題を議論する場合、運用環境で失敗したリクエストに関する情報を取得する方法に悩んでいる人がいるかもしれません。幸いにして、symfonyは<code>sfErrorLoggerPlugin</code>プラグインを利用できます。<code>sfErrorLoggerPlugin</code>プラグインは運用環境でバックグラウンドで動作し、データベースに404エラーと500エラーの詳細を記録します。ファイルのロギング機能よりもずっと速いです。いったんロギングメカニズムがオンになると、レベルが何であれ無視できないオーバーヘッドを追加するのに対して、プラグインのメソッドはリクエストが失敗したときのみ呼び出されるからです。<a href="http://trac.symfony-project.org/wiki/sfErrorLoggerPlugin">http://trac.symfony-project.org/wiki/sfErrorLoggerPlugin</a>でインストールの手引きのマニュアルを確認してください。</p>

<blockquote class="tip"><p>
  定期的にサーバーエラーのログをかならず確認してください。これらは404エラーと500エラーに関するとても価値のある情報も含みます。</p>
</blockquote>

<a name="optimizing.your.code" id="optimizing.your.code"></a><h2>コードを最適化する</h2>

<p>コード自身を最適化することでアプリケーションを加速することも可能です。このセクションはこれを行う方法に関するいくつかの洞察を提供します。</p>

<a name="core.compilation" id="core.compilation"></a><h3>コアコンパイレーション</h3>

<p>10個のファイルをロードすることは1個の長いファイルをロードするよりも多くのI/Oオペレーションが必要です。とても長いファイルのロードは小さなファイルのロードよりも多くのリソースを必要とします。とりわけファイルの内容の大部分がPHPパーサーに無意味な場合、これはコメントがあてはまります。</p>

<p>多くのファイルを統合してこれらに含まれるコメントをとり除けばパフォーマンスの改善につながります。symfonyはその最適化の機能をすでに持ちます; この機能はコアコンパイレーション(core compilation)と呼ばれます。最初のリクエストの始めに(もしくはキャッシュがクリアされた後に)、symfonyのアプリケーションはすべてのコアクラス(<code>sfActions</code>、<code>sfRequest</code>、<code>sfView</code>など)を1つのファイルに統合し、コメントと二重の空白を除去し、ファイルサイズの最適化を行い、このファイルをキャッシュと<code>config_core_compile.yml.php</code>ファイルに保存します。それぞれのつぎのリクエストでは30個のファイルの代わりにこれらの内容で構成され最適化された単独のファイルだけがロードされます。</p>

<p>アプリケーションがつねにロードしなければならないクラスを持つ場合、とりわけ、これらのクラスが多くのコメントを持つ場合、これらのクラスをコアコンパイルファイルに追加することが有益であることがあります。そのためには、リスト18-22のように、アプリケーションの<code>config/</code>ディレクトリに<code>core_compile.yml</code>ファイルを追加し、追加したいクラスの一覧を作ります。</p>

<p>リスト18-22 - コアコンパイレーションファイルにクラスを追加する(<code>frontend/config/core_compile.yml</code>)</p>

<pre><code>- %SF_ROOT_DIR%/lib/myClass.class.php
- %SF_ROOT_DIR%/apps/frontend/lib/myToolkit.class.php
- %SF_ROOT_DIR%/plugins/myPlugin/lib/myPluginCore.class.php
...
</code></pre>

<a name="the.sfoptimizer.plugin" id="the.sfoptimizer.plugin"></a><h3>sfOptimizerプラグイン</h3>

<p>symfonyは<code>sfOptimizer</code>という別の最適化ツールも提供します。このプラグインはsymfonyとアプリケーションのコードに最適化戦略を適用し、実行速度をさらに加速します。</p>

<p>symfonyのコードは設定パラメーターに依存する多くのチェック作業を含みます。アプリケーションもこの作業を行うことがあります。たとえば、symfonyのクラスを見てみると、<code>sfLogger</code>オブジェクトを呼び出すまえに<code>sf_logging_enabled</code>パラメーターの値のテストがよく見つかります:</p>

<pre class="php"><span class="kw1">if</span> <span class="br0">&#40;</span>sfConfig<span class="sy0">::</span><span class="me2">get</span><span class="br0">&#40;</span><span class="st_h">'sf_logging_enabled'</span><span class="br0">&#41;</span><span class="br0">&#41;</span>
<span class="br0">&#123;</span>
   <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getContext</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">getLogger</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'Been there'</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre>

<p><code>sfConfig</code>レジストリがよく最適化されているとしても、リクエストごとの処理期間の<code>get()</code>メソッドの呼び出し回数は重要です。そしてこれは最後のパフォーマンスの勘定に入れられます。<code>sfOptimizer</code>の最適化戦略の1つは設定定数が実行時に変更されないかぎり、設定定数をそれらの値に置き換えることです。この場合、たとえば、<code>sf_logging_enabled</code>パラメーターにあてはまります; このパラメーターが<code>false</code>に定義されていれば、<code>sfOptimizer</code>は以前のコードをつぎのように変換します:</p>

<pre class="php"><span class="kw1">if</span> <span class="br0">&#40;</span><span class="nu0">0</span><span class="br0">&#41;</span>
<span class="br0">&#123;</span>
   <span class="re0">$this</span><span class="sy0">-&gt;</span><span class="me1">getContext</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">getLogger</span><span class="br0">&#40;</span><span class="br0">&#41;</span><span class="sy0">-&gt;</span><span class="me1">info</span><span class="br0">&#40;</span><span class="st_h">'Been there'</span><span class="br0">&#41;</span><span class="sy0">;</span>
<span class="br0">&#125;</span></pre>

<p>そしてこれはすべてではありません。前のように明確なテストは空の文字列に対しても最適化されているからです。</p>

<p>最適化を適用するために、最初に<a href="http://www.symfony-project.org/plugins/sfOptimizerPlugin">http://www.symfony-project.org/plugins/sfOptimizerPlugin</a>プラグインをインストールし、アプリケーションと環境を指定して<code>optimize</code>タスクを呼び出します:</p>

<pre><code>&gt; php symfony optimize frontend prod
</code></pre>

<p>ほかの最適化戦略をコードに適用したい場合、<code>sfOptimizer</code>プラグインがよい出発点になるかもしれません。</p>

<a name="summary" id="summary"></a><h2>まとめ</h2>

<p>symfonyはすでによく最適化されたフレームワークで、膨大なトラフィックを占めるWebサイトに問題なく対応できます。アプリケーションのパフォーマンスを本当に最適化する必要がある場合、設定(サーバーの設定、PHPの設定、もしくはアプリケーションの設定)を調整すれば少し速くなります。効率的なモデルメソッドを書くためによい習慣にも従うべきです。データベースはWebアプリケーションのボトルネックになることが多いので、この点にすべての注意を払うべきです。テンプレートはいくつかのトリックからも恩恵を受けますが、もっとも速いのはつねにキャッシュによるものです。最後に、既存のプラグインを探すことにためらわないでください。これらのなかにはWebページの配信をもっと加速する革新的な技術を提供するものがあるからです(<code>sfSuperCache</code>、<code>sfOptimizer</code>など)。</p>
</div>
<div class="navigation">
<hr/>
<table width="100%">
<tr>
<td width="40%" align="left"><a href="17-Extending-Symfony.html">前の章</a></td>
<td width="20%" align="center"><a href="index.html">ホーム</a></td>
<td width="40%" align="right"><a href="19-Mastering-Symfony-s-Configuration-Files.html">次の章</a></td>
</tr>
</table>

</div>
</body>

</html>
